/**
 * 用来构建的类
 */
const path = require('path')
const fs = require('fs')
const parser = require('@babel/parser')
// 因为是ES6导出，所以要default一下
const traverse = require('@babel/traverse').default
const generator = require('@babel/generator').default
const ejs = require('ejs')
class Compiler {
    constructor(config) {
        this.config = config
        this.entry = config.entry
        // 获取执行 webpack-demo 指令的目录
        this.root = process.cwd()
        // 初始化一个空对象，存放所有的模块
        this.modules = {}
        // 将module下的rules挂载
        this.rules = this.config.module.rules
    }
    getSource (path) {
        return fs.readFileSync(path, 'utf-8')
    }
    depAnalyse (modulePath) {
        // 读取模块内容
        let source = this.getSource(modulePath)

        // 变为监听函数，里面的this就有了
        let handlerLoader = (use, obj) => {
            // console.log(use[j])
            // 获取loader文件路径，加载文件方法
            let laoderFn = require(path.join(this.root, use))
            // 执行方法，传递source进去
            // obj,如果是undefined,相当于没穿
            source = laoderFn.call(obj, source)
        }
        // 读取rules规则进行倒叙便利
        // 获取每一天规则，与当前modulePath进行匹配
        for(let i = this.rules.length -1 ; i >=0; i--) {
            // console.log(this.rules[i])
            let { test, use } = this.rules[i]
            // 匹配modulesPath 是否匹配规则，如果符合规则，倒叙便利所有的loader
            if(test.test(modulePath)) {
                // use：数组
                if (Array.isArray(use)) {
                    for(let j = use.length-1; j >=0; j--) {
                    // console.log(use[j])
                    // 获取loader文件路径，加载文件方法
                    // let laoderFn = require(path.join(this.root, use[j]))
                    // // 执行方法，传递source进去
                    // console.log(laoderFn, source)
                    // source = laoderFn(source)
                    handlerLoader(use[j])
                }
                } // 字符串
                else if (typeof use === 'string') {
                    // 获取loader文件路径，加载文件方法
                    // let laoderFn = require(path.join(this.root, use))
                    // // 执行方法，传递source进去
                    // console.log(laoderFn, source)
                    // source = laoderFn(source)
                    handlerLoader(use)
                }
                // 对象
                else if (use instanceof Object) {
                    // let laoderPath = path.join(this.root, use.loader)
                    // let loader = require(laoderPath)
                    // let options = use.options || {}
                    // source = loader.call({query: options}, source)
                    handlerLoader(use.loader, {query: use.options})
                }
            }
        }

        // 准备一个依赖数组，用于存储当前模块的依赖
        let dependencies = []
        // 解析为AST：抽象语法树
        let ast = parser.parse(source)
        // 操作抽象语法树，替换方法
        traverse(ast, {
            // 回调：P 就是抽象语法树找到的节点
            CallExpression(p) {
                if (p.node.callee.name === 'require') {
                    // 替换方法 __webpack_require__
                    p.node.callee.name = '__webpack_require__'
                    // 替换路径
                    let oldValue = p.node.arguments[0].value
                    oldValue = './' + path.join('src', oldValue)
                    // 避免Windows出现斜杠问题 \\ 
                    p.node.arguments[0].value = oldValue.replace(/\\+/g, '/')
                    // 每找到一个require，就讲其中的路径修改完毕后，加入到依赖数组中
                    dependencies.push(p.node.arguments[0].value)
                }
            }
        })
        // 转换为替换后的源码
        let sourceCode = generator(ast).code
        // 构建modules对象
        // path.relative：根据2个路径的差值，获取 './scr/index.js'
        let modulePathRelative = './' + path.relative(this.root, modulePath)
        modulePathRelative = modulePathRelative.replace(/\\+/g, '/')
        this.modules[modulePathRelative] = sourceCode
        // 递归加载所有的依赖
        dependencies.forEach(dep => this.depAnalyse(path.resolve(this.root, dep)))
    }
    // start 方法
    start () {
        // 开始打包了
        // 依赖的分析
        // __dirname 表示的是 webpack-demo 项目中 Complier.js 文件所在的目录
        // 而非入口文件所在的目录
        // 如果要获取执行 webpack-demo 指令所在的目录,需要使用 process.cwd()
        this.depAnalyse(path.resolve(this.root, this.entry))
        // 构建好的modules
        // console.log(this.modules)
        // 文件输出
        this.emitFile()
    }
    emitFile () {
        let template = this.getSource(
            path.join(__dirname, '../template/output.ejs')
        )
        let result = ejs.render(template, {
            entry: this.entry,
            modules: this.modules
        })
        // 读取webpack的optput配置，写入文件
        let outputPath =  path.join(this.config.output.path, this.config.output.filename)
        fs.writeFileSync(outputPath, result)
    }
}
module.exports = Compiler